#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "input_manager.h"
#include "msg_manager.h"
#include "board.h"
#include "task_manager.h"
#include "sys_clock.h"

#define TAG "INPUT_MANAGER"

#define MAX_KEY_SUPPORT					10
#define MAX_HOLD_KEY_SUPPORT        	4
#define MAX_MUTIPLE_CLICK_KEY_SUPPORT   4

#define LONG_PRESS_TIMER 			10
#define SUPER_LONG_PRESS_TIMER      50    	/* time */
#define SUPER_LONG_PRESS_6S_TIMER   150   	/* time */
#define QUICKLY_CLICK_DURATION 		300 	/* ms */
#define KEY_EVENT_CANCEL_DURATION 	50 		/* ms */

struct input_manager_info {
	esp_timer_handle_t work_item;
	esp_timer_handle_t hold_key_work;
	event_trigger event_cb;
	uint32_t press_type;
	uint32_t press_code;
	uint32_t report_key_value;
	int64_t report_stamp;
	uint16_t press_timer : 12;
	uint16_t click_num    : 4;
	bool input_manager_lock;
	bool key_hold;
	bool filter_itself;   /* filter all key event after current key event*/
};

#ifdef HOLDABLE_KEY
const char support_hold_key[MAX_HOLD_KEY_SUPPORT] = HOLDABLE_KEY;
#else
const char support_hold_key[MAX_HOLD_KEY_SUPPORT] = {0};
#endif

#ifdef MUTIPLE_CLIK_KEY
const char support_mutiple_key[MAX_MUTIPLE_CLICK_KEY_SUPPORT] = MUTIPLE_CLIK_KEY;
#else
const char support_mutiple_key[MAX_MUTIPLE_CLICK_KEY_SUPPORT] = {0};
#endif

struct input_manager_info *input_manager;

void sys_event_report_input(uint32_t key_event)
{
	struct app_msg msg = {0};

	msg.type = MSG_KEY_INPUT;
	msg.value = key_event;

	msg_manager_send_async_msg(MAIN_TASK_NAME, &msg);
}

void report_key_event(void *work)
{
	struct input_manager_info *input = input_manager;

	if (input_manager->filter_itself) {
		if ((input->report_key_value & KEY_TYPE_SHORT_UP)
			|| (input->report_key_value & KEY_TYPE_DOUBLE_CLICK)
			|| (input->report_key_value & KEY_TYPE_TRIPLE_CLICK)
			|| (input->report_key_value & KEY_TYPE_LONG_UP)) {
			input_manager->filter_itself = false;
		}

		return;
	}

#ifdef CONFIG_INPUT_DEV_ACTS_ADC_SR
	if (input->press_type == EV_SR) {
		sys_event_report_srinput(&input->report_key_value);
		return ;
	}
#endif

	input->click_num = 0;
	sys_event_report_input(input->report_key_value);
}

void sys_key_event_cb(uint32_t key_value, uint16_t type)
{
	if ((key_value & KEY_TYPE_DOUBLE_CLICK) == KEY_TYPE_DOUBLE_CLICK
		|| (key_value & KEY_TYPE_TRIPLE_CLICK) == KEY_TYPE_TRIPLE_CLICK
		|| (key_value & KEY_TYPE_SHORT_UP) == KEY_TYPE_SHORT_UP
		|| (key_value & KEY_TYPE_LONG_DOWN) == KEY_TYPE_LONG_DOWN) {
		/** need process key tone */
	#ifdef CONFIG_PLAY_KEYTONE
		//key_tone_play();
	#endif
	}

	/*filter onoff key for power on */
	if (((key_value & ~KEY_TYPE_ALL) == KEY_POWER)
		&& (esp_uptime_get_ms() < 1800))	{
		input_manager_filter_key_itself();
	}
}

static bool is_support_hold(int key_code)
{
	for (int i = 0 ; i < MAX_HOLD_KEY_SUPPORT; i++) {
		if (support_hold_key[i] == key_code)	{
			return true;
		}
	}
	return false;
}

#if CONFIG_INPUT_MUTIPLE_CLICK
static bool is_support_mutiple_click(int key_code)
{
	for (int i = 0 ; i < MAX_MUTIPLE_CLICK_KEY_SUPPORT; i++) {
		if (support_mutiple_key[i] == key_code)	{
			return true;
		}
	}
	return false;
}
#endif

void key_event_handle(struct device *dev, struct input_value *val)
{
	bool need_report = false;

	if (val->type != EV_KEY) {
		ESP_LOGE(TAG, "input type %d not support\n", val->type);
		return;
	}

	switch (val->value) {
	case KEY_VALUE_UP:
	{
		input_manager->press_code = val->code;
		input_manager->key_hold = false;
		if (input_manager->press_type == KEY_TYPE_LONG_DOWN
			 || input_manager->press_type == KEY_TYPE_LONG
			 || input_manager->press_type == KEY_TYPE_LONG6S) {
			input_manager->press_type = KEY_TYPE_LONG_UP;
		} else {
		#if CONFIG_INPUT_MUTIPLE_CLICK
			if (is_support_mutiple_click(input_manager->press_code)) {
				if ((input_manager->report_key_value == (input_manager->press_type | input_manager->press_code))
					&& esp_uptime_delta_ms(&input_manager->report_stamp) <= QUICKLY_CLICK_DURATION) {
					esp_timer_stop(input_manager->work_item);
					input_manager->click_num++;
					input_manager->report_stamp = esp_uptime_get_ms();
				}
				switch (input_manager->click_num) {
				case 0:
					input_manager->press_type = KEY_TYPE_SHORT_UP;
					break;
				case 1:
					input_manager->press_type = KEY_TYPE_DOUBLE_CLICK;
					break;
				case 2:
					input_manager->press_type = KEY_TYPE_TRIPLE_CLICK;
					break;
				}
			} else {
				input_manager->press_type = KEY_TYPE_SHORT_UP;
			}
		#else
			input_manager->press_type = KEY_TYPE_SHORT_UP;
		#endif
		}
		input_manager->press_timer = 0;
		need_report = true;
		break;
	}
	case KEY_VALUE_DOWN:
	{
		if (val->code != input_manager->press_code) {
			input_manager->press_code = val->code;
			input_manager->press_timer = 0;
			input_manager->press_type = 0;
			input_manager->filter_itself = false;
		} else {
			input_manager->press_timer++;

			if (input_manager->press_timer >= SUPER_LONG_PRESS_6S_TIMER) {
				if (input_manager->press_type != KEY_TYPE_LONG6S) {
					input_manager->press_type = KEY_TYPE_LONG6S;
					need_report = true;
				}
			} else if (input_manager->press_timer >= SUPER_LONG_PRESS_TIMER) {
				if (input_manager->press_type != KEY_TYPE_LONG) {
					input_manager->press_type = KEY_TYPE_LONG;
					need_report = true;
				}
			} else if (input_manager->press_timer >= LONG_PRESS_TIMER)	{
				if (input_manager->press_type != KEY_TYPE_LONG_DOWN) {
					input_manager->press_type = KEY_TYPE_LONG_DOWN;
					if (is_support_hold(input_manager->press_code)) {
						input_manager->key_hold = true;
						esp_timer_start_once(input_manager->hold_key_work, 500 * 1000);
					}
					need_report = true;
				}
			}

		}
		break;
	}
	default:
		break;
	}

	if (need_report) {
		input_manager->report_key_value = input_manager->press_type
									| input_manager->press_code;
		if (input_manager->event_cb) {
			input_manager->event_cb(input_manager->report_key_value, EV_KEY);
		}

		if (input_manager_islock()) {
			ESP_LOGE(TAG, "input manager locked\n");
			return;
		}

		// if (msg_pool_get_free_msg_num() <= (CONFIG_NUM_MBOX_ASYNC_MSGS / 2)) {
		// 	ESP_LOGE(TAG, "drop input msg ... %d", msg_pool_get_free_msg_num());
		// 	return;
		// }

	#if CONFIG_INPUT_MUTIPLE_CLICK
		if (is_support_mutiple_click(input_manager->press_code)) {
			input_manager->report_stamp = esp_uptime_get_ms();
			esp_timer_start_once(input_manager->work_item, (QUICKLY_CLICK_DURATION + KEY_EVENT_CANCEL_DURATION) * 1000);
		} else {
			esp_timer_start_once(input_manager->work_item, 1000);
		}
	#else
		esp_timer_start_once(input_manager->work_item, 1000);
	#endif
	}
}

#ifdef CONFIG_ENCODER_INPUT

void encoder_event_handle(struct device *dev, struct input_value *val)
{
	bool need_report = false;

	if (val->type != EV_KEY) {
		ESP_LOGE(TAG, "input type %d not support\n", val->type);
		return;
	}

	if(val->value == 0)
		val->code = KEY_VOLUMEUP;
	else
		val->code = KEY_VOLUMEDOWN;

	input_manager->press_code = val->code;
	input_manager->key_hold = false;
	input_manager->press_type = KEY_TYPE_SHORT_UP;
	input_manager->press_timer = 0;
	need_report = true;

	if (need_report) {
		input_manager->report_key_value = input_manager->press_type
									| input_manager->press_code;
		if (input_manager->event_cb) {
			input_manager->event_cb(input_manager->report_key_value, EV_KEY);
		}

		if (input_manager_islock()) {
			ESP_LOGE(TAG, "input manager locked\n");
			return;
		}

		// if (msg_pool_get_free_msg_num() <= (CONFIG_NUM_MBOX_ASYNC_MSGS / 2)) {
		// 	ESP_LOGE(TAG, "drop input msg ... %d", msg_pool_get_free_msg_num());
		// 	return;
		// }

		esp_timer_start_once(input_manager->work_item, 1000);
	}
}

#endif
static void check_hold_key(void *work)
{
	struct app_msg  msg = {0};

	msg.type = MSG_KEY_INPUT;
	if (input_manager->key_hold) {
		esp_timer_start_once(input_manager->hold_key_work, 200 * 1000);

		if (!input_manager_islock()) {
			msg.value = KEY_TYPE_HOLD | input_manager->press_code;
			if (input_manager->event_cb) {
				input_manager->event_cb(msg.value, EV_KEY);
			}

			if (input_manager->filter_itself) {
				return;
			}
			msg_manager_send_async_msg(MAIN_TASK_NAME, &msg);
		}
	} else {
		if (!input_manager_islock()) {
			msg.value = KEY_TYPE_HOLD_UP | input_manager->press_code;
			if (input_manager->event_cb) {
				input_manager->event_cb(msg.value, EV_KEY);
			}
			if (input_manager->filter_itself) {
				input_manager->filter_itself = false;
				return;
			}
			msg_manager_send_async_msg(MAIN_TASK_NAME, &msg);
		}
	}
}

#ifdef CONFIG_INPUT_DEV_ACTS_ADC_SR
void sr_event_handle(struct device *dev, struct input_value *val)
{
	bool need_report = false;

	if (val->type != EV_SR) {
		ESP_LOGE(TAG, "input type %d not support\n", val->type);
		return;
	}

	/* note: not compatible with adckey hold key */

	input_manager->press_type = (uint32_t)val->type;
	input_manager->key_hold = false;
	input_manager->press_timer = 0;
	need_report = true;

	if (need_report) {
		input_manager->report_key_value = (val->code << 16) | val->value;

		if (input_manager->event_cb) {
			input_manager->event_cb(input_manager->report_key_value, EV_SR);
		}

		if (input_manager_islock()) {
			ESP_LOGE(TAG, "input manager locked\n");
			return;
		}

		// if (msg_pool_get_free_msg_num() <= (CONFIG_NUM_MBOX_ASYNC_MSGS / 2)) {
		// 	ESP_LOGE(TAG, "drop input msg ... %d", msg_pool_get_free_msg_num());
		// 	return;
		// }

		esp_timer_start_once(input_manager->work_item, 1000);
	}
}
#endif

void* key_device_open(key_notify_cb cb, char* dev_name)
{
	struct key_handle * handle = malloc(sizeof(struct key_handle));
	if (handle == NULL) {
		ESP_LOGE(TAG, "adc key in malloc failed  need %d  bytes\n", (int)sizeof(struct key_handle));
		return NULL;
	}

	handle->input_dev = device_get_binding(dev_name);

	if (!handle->input_dev) {
		ESP_LOGE(TAG, "cannot found key dev %s\n",dev_name);
		free(handle);
		return NULL;
	}

	input_dev_enable(handle->input_dev);

	input_dev_register_notify(handle->input_dev, cb);

	handle->key_notify = cb;

	return handle;
}

static struct input_manager_info global_input_manager;

/*init manager*/
bool input_manager_register(event_trigger event_cb)
{
	input_manager = &global_input_manager;

	memset(input_manager, 0, sizeof(struct input_manager_info));

	esp_timer_create_args_t work_item_args = {
		.callback = (esp_timer_cb_t)report_key_event,
		.arg = NULL,
		.dispatch_method = ESP_TIMER_TASK,
		.name = "work_item",
		.skip_unhandled_events = false
	};
	esp_timer_create(&work_item_args, &input_manager->work_item);

	esp_timer_create_args_t hold_key_work_args = {
		.callback = (esp_timer_cb_t)check_hold_key,
		.arg = NULL,
		.dispatch_method = ESP_TIMER_TASK,
		.name = "hold_key_work",
		.skip_unhandled_events = false
	};
	esp_timer_create(&hold_key_work_args, &input_manager->hold_key_work);

	input_manager->event_cb = event_cb;

	if (!key_device_open(&key_event_handle, "gpio_key")) {
		ESP_LOGE(TAG, "adckey devices open failed\n");
		return false;
	}

#ifdef CONFIG_ENCODER_INPUT
		if (!key_device_open(&encoder_event_handle, "encoder_ec11")) {
			ESP_LOGE(TAG, "encoder_ec11 devices open failed\n");
		}
#endif

#ifdef CONFIG_INPUT_DEV_ACTS_IRKEY
	if (!key_device_open(&key_event_handle, "irkey")) {
		ESP_LOGE(TAG, "irkey devices open failed\n");
	}
#endif

#ifdef CONFIG_INPUT_DEV_ACTS_ADC_SR
	if (!key_device_open(&sr_event_handle, CONFIG_INPUT_DEV_ACTS_ADC_SR_NAME)) {
		ESP_LOGE(TAG, "adcsr devices open failed\n");
	}
#endif

	ESP_LOGI(TAG, "init success\n");
	input_manager_lock();

	return true;
}

void input_manager_init(void)
{
	input_manager_register(sys_key_event_cb);
	input_manager_unlock();
}

static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

bool input_manager_lock(void)
{
	portENTER_CRITICAL(&mux);
	input_manager->input_manager_lock = true;
	portEXIT_CRITICAL(&mux);
	
	return true;
}

bool input_manager_unlock(void)
{
	portENTER_CRITICAL(&mux);
	input_manager->input_manager_lock = false;
	portEXIT_CRITICAL(&mux);
	
	return true;
}

bool input_manager_islock(void)
{
	bool result = false;
	portENTER_CRITICAL(&mux);
	result = input_manager->input_manager_lock;
	portEXIT_CRITICAL(&mux);
	
	return result;
}

bool input_manager_filter_key_itself(void)
{
	portENTER_CRITICAL(&mux);
	input_manager->filter_itself = true;
	portEXIT_CRITICAL(&mux);
	
	return true;
}

